/**
 * @author Tobias Boehm
 *
 * 30.05.2011
 */
package org.anddev.andengine.extension.tbsengine.match;

import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import org.anddev.andengine.engine.Engine;
import org.anddev.andengine.entity.layer.tiled.tmx.util.exception.TMXLoadException;
import org.anddev.andengine.extension.tbsengine.faction.AbsBaseFaction;
import org.anddev.andengine.extension.tbsengine.map.AbsMap;
import org.anddev.andengine.extension.tbsengine.match.gamestate.AbsPlayerGameState;
import org.anddev.andengine.extension.tbsengine.match.gamestate.AbsServerGlobalGameState;
import org.anddev.andengine.extension.tbsengine.message.client.AbsClientMatchMessage;
import org.anddev.andengine.extension.tbsengine.message.server.ingame.AbsServerMatchReconnectRequestReplyMessage;
import org.anddev.andengine.extension.tbsengine.message.server.ingame.ServerMatchIngameVerificationRequestReplyMessage;
import org.anddev.andengine.extension.tbsengine.player.BaseHumanPlayer;
import org.anddev.andengine.extension.tbsengine.player.BasePlayer;
import org.anddev.andengine.extension.tbsengine.player.BaseServerHumanPlayer;
import org.anddev.andengine.extension.tbsengine.server.AbsGameServer;
import org.anddev.andengine.util.path.Path.Step;
import org.xml.sax.SAXException;

import android.content.Context;


/**
 * This is the abstract implementation of the server side version of
 * {@link AbsMatch}. You may implement this class on your game server.
 *  
 * 
 * @author Tobias Boehm
 *
 * @param <ServerGlobalGameStateClass> - Your implementation of {@link AbsServerGlobalGameState}.
 * @param <MapClass> - Your implementation of {@link AbsMap}.
 * @param <PlayerGameStateClass> - Your implementation of {@link AbsPlayerGameState}.
 * @param <GameServerClass> - Your implementation of {@link AbsGameServer}
 * @param <BaseFactionClass> - Your implementation of {@link AbsBaseFaction}
 * @param <ServerMatchReconnectionRequestReplyMessageClass> - Your implementation of {@link AbsServerMatchReconnectRequestReplyMessage}
 * @param <MatchPreparationClass> - Your implementation of {@link AbsServerMatchPreparation}.
 */
public abstract class AbsServerMatch<ServerGlobalGameStateClass extends AbsServerGlobalGameState<?,?,PlayerGameStateClass,BaseFactionClass>, 
									 MapClass extends AbsMap,
									 PlayerGameStateClass extends AbsPlayerGameState<BaseFactionClass>,
									 GameServerClass extends AbsGameServer,
									 BaseFactionClass extends AbsBaseFaction,
									 ServerMatchReconnectionRequestReplyMessageClass extends AbsServerMatchReconnectRequestReplyMessage,
								     MatchPreparationClass extends AbsServerMatchPreparation<GameServerClass,MatchPreparationClass,BaseFactionClass,?,?,?>>

									 extends AbsMatch<PlayerGameStateClass,ServerGlobalGameStateClass,MapClass>{
			
	/**
	 * A reference to the game server this match is running on.
	 */
	protected final GameServerClass mGameServer;
	
	/**
	 * A reference to the match preparation object which contains all
	 * necessary information to create this match.
	 */
	protected final MatchPreparationClass mMatchPreparation;
	
	/**
	 * A set of all player IDs of players which will reconnect to this match.
	 */
	protected final Set<UUID> mExpectedClients = Collections.synchronizedSet(new HashSet<UUID>());
	
	
	/**
	 * This is the default constructor of this class. It will create an instance
	 * of a server match according to the information stored inside the match 
	 * preparation object which is provided as a parameter.
	 * 
	 * 
	 * @param pContext
	 * @param pEngine - A reference to the game engine.
	 * @param pMatchPreparation - A reference to the match preparation object this match will be based on.
	 * @param pGameServer - A reference to the game server this match is running on.
	 * 
	 * @throws IOException This exception is thrown if {@link #loadMap(String, Context, Engine)} is unable to load the map file.
	 * @throws SAXException This exception is thrown if {@link #loadMap(String, Context, Engine)} is unable to parse the map file.
	 * @throws TMXLoadException This exception is thrown if {@link #loadMap(String, Context, Engine)} discovers a problem with the map file format.
	 */
	public AbsServerMatch(final Context pContext, final Engine pEngine, final MatchPreparationClass pMatchPreparation, final GameServerClass pGameServer) throws TMXLoadException, IOException, SAXException{
		
		super(pContext, pEngine, pMatchPreparation.getMatchID(),pMatchPreparation.getMapOverview().getMapID());
		
		this.mGameServer = pGameServer;
		
		this.mMatchPreparation = pMatchPreparation;
		
		this.initAdditionalComponents(pMatchPreparation);
		
		for(BasePlayer player : pMatchPreparation.getJoinedPlayers()){
			
			if(player instanceof BaseHumanPlayer){
				
				this.mExpectedClients.add(player.getPlayerID());
			}
		}
		
		this.mMatchState = MatchState.LOOKING_FOR_PLAYERS;
		
		this.initMatch();
	}
	
	/**
	 * This method should process a player message which contains a part of a 
	 * match (a {@link Turn} or a {@link Step}). Inside this method you should 
	 * decide whether the provided part is valid or not. You should use
	 * {@link #validateTurn(Turn)} or {@link #validateStep(AbsStep)} for this
	 * purpose.
	 *  
	 * @param pClientMessage - The player message containing the part of a match which should be validated. 
	 * @param pReply - The reply to this validation request.
	 */
	public abstract void processClientMatchVerificationMessage(final AbsClientMatchMessage pClientMessage, final ServerMatchIngameVerificationRequestReplyMessage pReply);
		
	/**
	 * This method should process a player reconnect request.
	 * <p>
	 * Manual:<br>
	 * First you should check whether or not this match is in 
	 * {@link MatchState#LOOKING_FOR_PLAYERS} or {@link MatchState#RUNNING}
	 * state.
	 * <p>
	 * If the match is in {@link MatchState#LOOKING_FOR_PLAYERS} you should use
	 * {@link #mExpectedClients} to check whether or not this player is allowed
	 * to reconnect to this match. If the player is welcome you should remove 
	 * the player from {@link #mExpectedClients}. If all players have reported
	 * back you should call <code>this.mGlobalGameState.startMatch()</code> to
	 * initialize the first round.<br>
	 * <p>
	 * If the match is in {@link MatchState#RUNNING} you may check if the player
	 * is part of the current match session and rejoin him.
	 * <p>
	 * Notice:<br>
	 * Don't forget to put your decision and match-data into the given
	 * {@link AbsServerMatchReconnectRequestReplyMessage}.
	 *  
	 * 
	 * @param pPlayer - The player who wants to reconnect to this match.
	 * @param pReply - The reply to the user.
	 */
	public abstract void processClientMatchReconnectRequest(final BaseServerHumanPlayer pPlayer, final ServerMatchReconnectionRequestReplyMessageClass pReply);
	
	/**
	 * This method validates a {@link Turn}. A {@link Turn} is valid if all of 
	 * its {@link Step}s are valid.
	 * 
	 * @param currentTurn - The {@link Turn} which should be validated.
	 * 
	 * @return true if the {@link Turn} is valid otherwise false.
	 */
	protected boolean validateTurn(final Turn currentTurn) {
		
		for(AbsStep currentStep : currentTurn.getSteps()){
			
			if(!this.validateStep(currentStep)){
				
				return false;
			}
		}
		
		return true;
	}

	/**
	 * This method should validate a {@link Step}.
	 * 
	 * @param currentStep - The {@link Step} which should be validated.
	 * 
	 * @return true if the {@link Step} is valid otherwise false.
	 */
	protected abstract boolean validateStep(final AbsStep currentStep);

	/**
	 * This method should announce all necessary information about the of this
	 * match (for example the winner or statistics) to all connected players.
	 */
	public abstract void announceMatchCompleted();
	
	/**
	 * This method should announce all necessary information about the start
	 * of a new round to all connected players.
	 * 
	 * @param pCurrentRound - The new round.
	 */
	public abstract void announceRoundStart(final Round<PlayerGameStateClass> pCurrentRound);
	
	/**
	 * This method should announce all necessary information about the end of
	 * the current {@link Round} to all connected players.
	 * 
	 * @param pCurrentRound - The round which is now complete.
	 */
	public abstract void announceRoundCompleted(final Round<PlayerGameStateClass> pCurrentRound);
	
	/**
	 * This method should announce all necessary information about the start of 
	 * a new {@link Turn} to all connected players.
	 * 
	 * @param pCurrentTurn - The new turn.
	 */
	public abstract void announceTurnStart(final Turn pCurrentTurn);
	
	/**
	 * This method should announce all necessary information about the end of 
	 * the current {@link Turn} to all connected players.
	 * 
	 * @param pCompletedTurn - The turn which is complete.
	 */
	public abstract void announceTurnComplete(final Turn pCompletedTurn);
	
	/**
	 * This method should announce all necessary information about the 
	 * {@link Step}* which was completed. 
	 *  
	 * @param pStep - The next step.
	 */
	public abstract void announceStepComplete(final AbsStep pStep);
	
	/**
	 * In this method you should initialize additional components (like member
	 * variables) which are unknown to the author of this framework. 
	 * <p>
	 * Warning: <br>
	 * For this purpose you should NOT use the constructor of your class because
	 * it is called AFTER the constructor (and some important method calls) of
	 * THIS class is completed. 
	 *    
	 * @param pMatchPreparation - The instance of your {@link AbsServerMatchPreparation}
     * 							implementation which may contains information 
     * 							to initialize additional components (like
     * 							member variables) which are unknown to the 
     * 							author of this framework. 
	 */
	protected abstract void initAdditionalComponents(final MatchPreparationClass pMatchPreparation);
	
	
	/**
	 * @return the mGameServer
	 */
	public GameServerClass getGameServer() {
		return mGameServer;
	}

	/**
	 * @return the mMatchPreparation
	 */
	public MatchPreparationClass getMatchPreparation() {
		return mMatchPreparation;
	}
}
